package org.apache.hadoop.hdfs.server.protocol;

import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
import org.apache.hadoop.hdfs.server.protocol.BlockRecoveryCommand.RecoveringBlock;
import org.apache.hadoop.security.KerberosInfo;

/**
 * Datanode与Datanode之间的接口。 InterDatanodeProtocol接口主要用于租约恢复操作
 *
 * 客户端打开一个文件进行写操作时，首先要获取这个文件的租约，并且还需要定期更新租约。
 * 当Namenode的租约监控线程发现某个HDFS文件租约长期没有更新时， 就会认为写这个文件的客户端发生异常，
 * 这时Namenode就需要触发租约恢复操作——同步数据流管道中所有Datanode上该文件数据块的状态， 并强制关闭这个文件。
 *
 * 租约恢复的控制并不是由Namenode负责的，而是Namenode从数据流管道中选出一个主恢复节点，
 * 然后通过下发DatanodeCommand的恢复指令触发这个数据节点控制租约恢复操作， 也就是由这个主恢复节点协调整个租约恢复操作的过程。
 *
 * 主恢复节点会调用 InterDatanodeProtocol接口来指挥数据流管道的其他数据节点进行租约恢复。
 *
 * 租约恢复操作其实很简单， 就是将数据流管道中所有数据节点上保存的同一个数据块状态(时间戳和 数据块长度)同步一致。
 * 当成功完成租约恢复后， 主恢复节点会调用 DatanodeProtocol.commitBlockSynchronization()方法,
 * 同步namenode节点上该数据块的时间戳和数据块长度，保持namenode和datanode的一致。
 *
 * 当完成了所有的同步操作后，
 * 主恢复节点节就可以调用DatanodeProtocol.commitBlockSynchronization() 将Namenode上该数据块的长度和时间戳同步为新的长度和时间戳，
 * 这样Datanode和Namenode的数据也就一致了。
 */
@KerberosInfo( serverPrincipal = DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, clientPrincipal = DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY)
@InterfaceAudience.Private
public interface InterDatanodeProtocol {
  public static final Log LOG = LogFactory.getLog(InterDatanodeProtocol.class);

  /**
   * 在版本9之前，这个类InterDatanodeProtocol既是DataNode的接口，也是用于与DataNode通信的RPC协议。
   * DataNode和DataNode都使用这个类来隔离协议序列化。
   * 如果要添加或更改DataNode的接口，则需要同时更改这个类和I中的相关协议缓冲区有线协议定义interdatanodeprotocol.proto文件.
   * 关协议缓冲线协议的更多详细信息，请参阅 .../org/apache/hadoop/hdfs/protocolPB/overview.html
   */
  public static final long versionID = 6L;

  /**
   * 由于数据流管道中同一个数据块状态(长度和时间戳)在不同的Datanode上可能是不一致的，
   * 所以主恢复节点会首先调用initReplicaRecovery()方法 获取数据流管道中所有数据节点上保存的指定数据块的状态， 这里的数据块状态使用 ReplicaRecoveryInfo类封装。
   * 主恢复节点会根据收集到的这些状态，确定一个当前数据块的新长度， 并且使用Namenode下发的recoverId作为数据块的新时间戳。
   */
  ReplicaRecoveryInfo initReplicaRecovery(RecoveringBlock rBlock) throws IOException;

  /**
   * 主恢复节点计算出数据块的新长度后，
   * 就会调用InterDatanodeProtocol.updateReplicaUnderRecovery()方法将数据流管道中所有节点上该数据块的长度同步为新的长度，将数据块的时间戳同步为新的时间戳。
   */
  String updateReplicaUnderRecovery(ExtendedBlock oldBlock, long recoveryId, long newLength) throws IOException;
}
